;
;  PROGRAM:  MCHECK
;  AUTHOR:  RICHARD CONN
;  VERSION:  1.1
;  DATE:  6 Jan 83
;  PREVIOUS VERSIONS:  1.0 (12 Dec 82)
;
VERS	EQU	11	;VERSION NUMBER

;
;	This program is Copyright (c) 1982, 1983 by Richard Conn
;	All Rights Reserved
;
;	ZCPR2 and its utilities, including this one, are released
; to the public domain.  Anyone who wishes to USE them may do so with
; no strings attached.  The author assumes no responsibility or
; liability for the use of ZCPR2 and its utilities.
;
;	The author, Richard Conn, has sole rights to this program.
; ZCPR2 and its utilities may not be sold without the express,
; written permission of the author.
;


;
;	MCHECK is used to check the syntax of a MENU.CPR file for the ZCPR2
; menu processor, MENU.  MENU was optimized for size and runtime speed, and
; I tried to keep the size under 2K (and succeeded, for that matter).  In
; keeping MENU small, the error diagnostics it gives are quite limited, with
; a variety of errors producing the message "Str Err" for MENU.CPR
; structure error.
;
;	MCHECK is intended to be used to check the syntax and other features
; of a user's MENU.CPR before allowing MENU to run with it.  In this way,
; many errors may be caught before the MENU.CPR file comes into common use,
; and there is plenty of space for informative diagnostics.
;

;
;  MENU Constants
;
MCMD	EQU	':'	;Menu Jump Command
RSM	EQU	'$'	;System Menu Indic
MINDIC	EQU	'#'	;Menu Indic
GOPTION	EQU	'-'	;Global Option Indic
COPTION	EQU	'C'	;Option chars
DOPTION	EQU	'D'
POPTION	EQU	'P'
XOPTION	EQU	'X'

;
;  CP/M Constants
;
bentry	equ	5	;BDOS Entry
fcb	equ	5ch	;FCB
tbuff	equ	80h	;Temp I/O Buffer
cr	equ	0dh
lf	equ	0ah
EOF	equ	'Z'-'@'	;^Z=EOF

;
;  SYSLIB Externals
;
	ext	caps
	ext	crlf
	ext	eval10
	ext	f$open
	ext	f$close
	ext	f$read
	ext	print
	ext	zgpins
	ext	zfname
	ext	retud
	ext	logud
	ext	moveb
	ext	cout
	ext	phldc
	ext	padc
	ext	codend

;
;  Branch to Start of Program
;
	jmp	start

;
;******************************************************************
;
;  SINSFORM -- ZCPR2 Utility Standard General Purpose Initialization Format
;
;	This data block precisely defines the data format for
; initial features of a ZCPR2 system which are required for proper
; initialization of the ZCPR2-Specific Routines in SYSLIB.
;

;
;  EXTERNAL PATH DATA
;
EPAVAIL:
	DB	0FFH	; IS EXTERNAL PATH AVAILABLE? (0=NO, 0FFH=YES)
EPADR:
	DW	40H	; ADDRESS OF EXTERNAL PATH IF AVAILABLE

;
;  INTERNAL PATH DATA
;
INTPATH:
	DB	0,0	; DISK, USER FOR FIRST PATH ELEMENT
			; DISK = 1 FOR A, '$' FOR CURRENT
			; USER = NUMBER, '$' FOR CURRENT
	DB	0,0
	DB	0,0
	DB	0,0
	DB	0,0
	DB	0,0
	DB	0,0
	DB	0,0	; DISK, USER FOR 8TH PATH ELEMENT
	DB	0	; END OF PATH

;
;  MULTIPLE COMMAND LINE BUFFER DATA
;
MCAVAIL:
	DB	0FFH	; IS MULTIPLE COMMAND LINE BUFFER AVAILABLE?
MCADR:
	DW	0FF00H	; ADDRESS OF MULTIPLE COMMAND LINE BUFFER IF AVAILABLE

;
;  DISK/USER LIMITS
;
MDISK:
	DB	4	; MAXIMUM NUMBER OF DISKS
MUSER:
	DB	31	; MAXIMUM USER NUMBER

;
;  FLAGS TO PERMIT LOG IN FOR DIFFERENT USER AREA OR DISK
;
DOK:
	DB	0FFH	; ALLOW DISK CHANGE? (0=NO, 0FFH=YES)
UOK:
	DB	0FFH	; ALLOW USER CHANGE? (0=NO, 0FFH=YES)

;
;  PRIVILEGED USER DATA
;
PUSER:
	DB	10	; BEGINNING OF PRIVILEGED USER AREAS
PPASS:
	DB	'chdir',0	; PASSWORD FOR MOVING INTO PRIV USER AREAS
	DS	41-($-PPASS)	; 40 CHARS MAX IN BUFFER + 1 for ending NULL

;
;  CURRENT USER/DISK INDICATOR
;
CINDIC:
	DB	'$'	; USUAL VALUE (FOR PATH EXPRESSIONS)

;
;  DMA ADDRESS FOR DISK TRANSFERS
;
DMADR:
	DW	80H	; TBUFF AREA

;
;  NAMED DIRECTORY INFORMATION
;
NDRADR:
	DW	00000H	; ADDRESS OF MEMORY-RESIDENT NAMED DIRECTORY
NDNAMES:
	DB	64	; MAX NUMBER OF DIRECTORY NAMES
DNFILE:
	DB	'NAMES   '	; NAME OF DISK NAME FILE
	DB	'DIR'		; TYPE OF DISK NAME FILE

;
;  REQUIREMENTS FLAGS
;
EPREQD:
	DB	0FFH	; EXTERNAL PATH?
MCREQD:
	DB	000H	; MULTIPLE COMMAND LINE?
MXREQD:
	DB	000H	; MAX USER/DISK?
UDREQD:
	DB	000H	; ALLOW USER/DISK CHANGE?
PUREQD:
	DB	000H	; PRIVILEGED USER?
CDREQD:
	DB	000H	; CURRENT INDIC AND DMA?
NDREQD:
	DB	0FFH	; NAMED DIRECTORIES?
Z2CLASS:
	DB	0	; CLASS 0
	DB	'ZCPR2'
	DS	10	; RESERVED

;
;  END OF SINSFORM -- STANDARD DEFAULT PARAMETER DATA
;
;******************************************************************
;

;
;  Start of Program
;
start:
	call	zgpins	;init ZCPR2 buffers
	lxi	h,tbuff	;place zero at end of input buffer
	mov	a,m	;get char count
	inx	h	;pt to first char
	push	h	;save ptr
	add	l
	mov	l,a
	mov	a,h
	aci	0
	mov	h,a
	mvi	m,0	;store ending zero
	pop	h	;pt to first char
	call	sblank	;skip over blanks
	lxi	d,inbuf	;copy into input line buffer
	push	d	;save ptr to first char
start0:
	mov	a,m	;get byte
	stax	d	;put it
	inx	h	;pt to next
	inx	d
	ora	a	;end of string?
	jnz	start0

	call	print
	db	'MCHECK  Version '
	db	(vers/10)+'0','.',(vers mod 10)+'0',0

	pop	h	;HL pts to first non-blank char
	mov	a,m	;get it
	ora	a	;EOL?
	jz	help
	cpi	'/'	;option?
	jnz	start1
;
;  Print Help Message
;
help:
	call	print
	db	cr,lf,'	MCHECK is used to perform a syntax check on a MENU.CPR'
	db	cr,lf,'file.  It is invoked with the following forms:'
	db	cr,lf,'		MCHECK or MCHECK //	<-- Print this Help'
	db	cr,lf,'		MCHECK dir:filename.typ	<-- Check File'
	db	cr,lf,'		MCHECK dir:filename	<-- Check filename.CPR'
	db	cr,lf,0
	ret

;
;  Begin serious processing -- locate the file pted to by HL
;
start1:
	call	retud		;get current user/disk
	mov	a,b		;save disk
	sta	cdisk
	mov	a,c		;save user
	sta	cuser
	lxi	d,mfcb		;pt to FCB
	call	zfname		;look for file
	jnz	start2
	call	print
	db	cr,lf,'Error in Disk or User Number -- Aborting',0
	ret
;
;  Set File Type to CPR if not specified
;
start2:
	lxi	h,mtyp		;pt to file type
	mov	a,m		;get first char
	cpi	' '		;set type if <SP>
	jnz	start3
	push	b		;save BC
	lxi	d,cprtyp	;set type to CPR
	xchg
	mvi	b,3		;3 bytes
	call	moveb
	pop	b		;get BC
;
;  Set User and Disk in C and B
;
start3:
	mvi	a,0ffh		;get current disk indicator
	cmp	b		;B=indicator?
	jnz	start4
	lda	cdisk		;select current disk
	mov	b,a		;... in B
	inr	b		;in range 1-16
start4:
	dcr	b		;adjust disk to 0-15
	mvi	a,0ffh		;get current user indicator
	cmp	c		;C=indicator?
	jz	start5
	mvi	a,'?'		;if C=All Users, default to current
	cmp	c
	jnz	start6
start5:
	lda	cuser		;select current user
	mov	c,a
start6:
	call	logud		;log in user and disk
	lxi	d,mfcb		;prepare to open file
	xra	a		;A=0 to select current disk
	stax	d
	call	f$open		;open file
	jz	readfile	;read in file if OK
	call	print
	db	cr,lf,'Error in Opening File -- Aborting',0
	ret
;
;  Read in File
;
readfile:
	call	codend		;get address of first block
readloop:
	lxi	d,mfcb		;read block
	call	f$read		;do it
	ora	a		;check for error
	jnz	readdone
	lxi	d,tbuff		;pt to block just read in
	mvi	b,128		;128 bytes
readmove:
	ldax	d		;get byte
	ani	7fh		;mask MSB
	mov	m,a		;put byte
	inx	h		;pt to next
	inx	d
	dcr	b		;count down
	jnz	readmove
	xchg			;DE pts to next block
	lhld	bentry+1	;get address of BDOS
	mov	a,h		;check for possible overflow
	sui	10		;10 pages below BDOS is limit
	cmp	d		;within range?
	xchg			;HL pts to next block
	jnc	readloop	;continue read if within range
	call	print
	db	cr,lf,'TPA Overflow -- MENU.CPR File is Too Big -- Aborting',0
	ret
;
;  Read is Done -- Store Ending ^Z and Set Initial Values
;
readdone:
	mvi	m,EOF	;Store ^Z to ensure EOF
	lxi	d,mfcb		;Close File
	call	f$close
	mvi	a,0ffh		;A = -1
	sta	menunum		;set menu number
	sta	maxnum		;set max number of all menus
	lxi	h,0		;HL=0
	shld	errors		;Set Error Count to 0
	inx	h		;HL=1
	shld	linenum		;Set Line Number to 1
;
;  Count Number of Menus
;
	call	codend		;Pt to First Byte
	mov	a,m		;get first byte
;
;  Skip to Beginning of Menu Display
;
mdskip:
	cpi	EOF		;EOF?
	jz	mdone
	cpi	MINDIC		;beginning of display?
	jz	mcgo		;now go skip commands
	call	lskip		;skip to next line
	jmp	mdskip
mcgo:
	inx	h		;pt to char after MINDIC
	mov	a,m		;another MINDIC?
	cpi	MINDIC
	jz	mdone		;done if 2 in a row
	lda	maxnum		;get menu number count
	inr	a		;found another one
	sta	maxnum
mcskip:
	call	lskip		;skip to next line
	jz	mdone		;done if premature EOF
	cpi	MINDIC		;end of display?
	jnz	mcskip
	inx	h		;pt to char after MINDIC
	mov	a,m		;get it
	jmp	mdskip
;
;  Check for Valid First Character
;
mdone:
	call	print		;Print Header
	db	cr,lf
	db	cr,lf,' Line Comment/Error Message'
	db	cr,lf,' ---- ---------------------',0

	xra	a		;set no global option
	sta	gopt
	call	codend		;get address of first byte
	mov	a,m		;get first char
	cpi	GOPTION		;global options?
	jnz	newmenu		;process globals
	mvi	a,0ffh		;set global option
	sta	gopt
	call	lprint
	db	'** Global Options Detected **',0
	call	optchk		;check options
	xra	a		;set no global option
	sta	gopt
	call	nxtline		;advance to next line
;
;  This is the main entry point for processing a menu
;
newmenu:
	mov	a,m		;get Menu Indicator
	cpi	MINDIC		;must be MINDIC
	jz	nm1
	call	newerr		;add to error count
	call	lprint
	db	'Error -- New Menu Expected, But ',MINDIC,' NOT Found -- '
	db	'Aborting',0
	jmp	errxit
;
;  Print that we have a new menu
;
nm1:
	call	lprint
	db	'** Menu Number ',0
	lda	menunum		;increment menu number
	inr	a
	sta	menunum
	call	padc
	call	optchk		;check options
;
;  Skip Thru Display
;
nm2:
	call	nxtline		;skip to next line
	jnz	nm2a		;continue if no EOF
earlyeof:
	call	newerr		;add to error count
	call	lprint
	db	'Error -- Premature EOF Encountered -- Aborting',0
	jmp	errxit
nm2a:
	cpi	MINDIC		;Menu Indicator?
	jnz	nm2		;Continue
;
;  Move Thru Menu Commands
;
nm3:
	call	nxtline		;skip to next line
	jz	earlyeof
	call	lcheck		;check line
	cpi	MINDIC		;check for menu indicator
	jnz	nm3		;continue until menu indicator encountered
	inx	h		;check for 2 indicators in a row for end
	mov	a,m		;get 2nd char
	dcx	h		;back up in case it is not
	cpi	MINDIC		;2 in a row?
	jnz	newmenu		;process as new menu if not
errxit:
	call	lprint
	db	'** End of Menu Check **',cr,lf,'	',0
	lhld	errors		;check error count
	mov	a,h		;check for Zero
	ora	l
	jnz	err1
	call	print
	db	'No',0
	jmp	err2
err1:
	call	phldc		;print as decimal
err2:
	call	print
	db	' Errors Detected',0
	ret

;
;  Utilities
;

;
;  LPRINT -- Print "Line # "+text
;
lprint:
	call	crlf		;new line
	push	h		;save HL
	lhld	linenum		;get line number
	call	phldc		;print as decimal
	pop	h		;restore HL
	mvi	a,' '		;print <sp>
	call	cout
	jmp	print		;print text
;
;  NXTLINE -- Advance to next line, check for EOF, and increment Line Number
;  LSKIP -- Advance to next line and check for EOF
;	Return with HL pting to first char of next line and Z Set if EOF
;
nxtline:
	push	h		;increment line count
	lhld	linenum		;add 1
	inx	h
	shld	linenum
	pop	h		;fall thru to skipping
lskip:
	mov	a,m		;get char
	cpi	EOF		;EOF?
	rz
	inx	h		;pt to next
	cpi	lf		;line feed?
	jnz	lskip		;continue if not
	mov	a,m		;get first char of next line
	cpi	EOF		;check for EOF
	ret
;
;  OPTCHK -- Check Line Pted to by HL for Valid GOPTION and MINDIC options
;	Do Not Affect HL
;	Print Error Message and Character if Invalid Option Found
;
optchk:
	push	h		;save HL
	push	b
	inx	h		;skip indicator
optclp:
	mov	a,m		;get char
	call	caps		;capitalize
	inx	h		;pt to next
	cpi	cr		;EOL?
	jz	optcdn
	mov	b,a		;char in B
	lda	gopt		;global option?
	ora	a		;0=no
	mov	a,b		;get char
	jnz	optcl1		;skip RSM test if it is global
	cpi	RSM		;System Menu?
	jz	optclp
optcl1:
	cpi	COPTION		;check options
	jz	optclp
	cpi	DOPTION
	jz	optclp
	cpi	POPTION
	jz	optclp
	cpi	XOPTION
	jz	optclp
	call	newerr		;increment error count
	call	lprint
	db	'Error -- Invalid Option: ',0
	mov	a,b		;get char
	call	cout		;print char
	jmp	optclp
optcdn:
	pop	b
	pop	h		;restore ptr
	ret
;
;  Increment Error Count
;
newerr:
	push	h	;save HL
	lhld	errors	;increment error count
	inx	h
	shld	errors
	pop	h	;restore HL
	ret
;
;  Check Line, especially looking for Menu Jump
;
lcheck:
	push	h	;save ptr to first char
	inx	h	;pt to 2nd char
	mov	a,m	;get it
	cpi	MCMD	;menu jump?
	jnz	lchk1
	inx	h	;pt to menu number
	call	eval10	;convert to binary in DE
	mov	a,d	;D must be 0
	ora	a	;check
	jz	lchk0
lchker:
	call	newerr	;increment error count
	call	lprint
	db	'Error -- Menu Number Out of Range',0
	jmp	lchk1
lchk0:
	lda	maxnum	;get max menu number
	cmp	e	;check for range
	jc	lchker
lchk1:
	pop	h	;restore ptr
	mov	a,m	;get first char in line
	ret
;
;  Skip HL over Blanks
;
sblank:
	mov	a,m	;get char
	inx	h	;pt to next
	cpi	' '	;blank?
	jz	sblank	;continue skipping
	dcx	h	;pt to non-blank
	ret

inbuf:
	ds	250	;input line buffer
cdisk:
	ds	1	;current disk
cuser:
	ds	1	;current user
mfcb:
	db	0
	ds	8	;file name
mtyp:
	ds	3	;file type
	ds	4
	ds	16
	ds	4
cprtyp:
	db	'CPR'
errors:
	ds	2	;error count
linenum:
	ds	2	;current line number
menunum:
	ds	1	;current menu number
maxnum:
	ds	1	;max menu number
gopt:
	ds	1	;global option flag

	end
